package progma.lang.compiler;

import java.util.ArrayList;
import java.util.HashMap;

import org.antlr.runtime.Token;
import org.antlr.runtime.TokenStream;

import progma.lang.compiler.exception.DuplicateVarException;
import progma.lang.compiler.exception.GlobalException;

// TODO: name refactor in some methods, fields
public class SymbolTable {

	private final String SEP = "@";
	private TokenStream input;
	private ArrayList<String> currentContext = new ArrayList<String>();
	private HashMap<String, ArrayList<String>> context = new HashMap<String, ArrayList<String>>();
	private HashMap<String, ArrayList<Global>> globals = new HashMap<String, ArrayList<Global>>();
	
	public SymbolTable(TokenStream input) {
		this.input = input;
	}
	
	public ArrayList<String> getContext() {
		return currentContext;
	}
	
	public String getCurrentContext() {
		return getCurrentContext(currentContext.size());
	}
	
	public String getCurrentContext(int depth) {
		String cc = "";
		if(currentContext.size() == 0)
			cc = "$basemodule";
		else
			cc = currentContext.get(0);
		int d = 1;
		while(d < depth)
			cc += SEP + currentContext.get(d++);
		
		return cc;
	}
	
	public String pop() {
		return currentContext.remove(currentContext.size() - 1);
	}
	
	public void push(Token context) {
		push(context.getText());
	}
	
	public void push(String context) {
		currentContext.add(context);
	}
	
	/**
	 * Adds a var to the current context.
	 * @param var
	 * @throws DuplicateVarException 
	 */
	public void addVar(Token var) throws DuplicateVarException {
		addVar(var.getText());
	}
	
	/**
	 * Adds a var to the current context.
	 * @param var
	 * @throws DuplicateVarException 
	 */
	public void addVar(String var) throws DuplicateVarException {
		String cc = getCurrentContext();
		if(!context.containsKey(cc)) 
			context.put(cc, new ArrayList<String>());
		ArrayList<String> arr = context.get(cc);
		if(!arr.contains(var))
			arr.add(var);
		else if(currentContext.size() == 1)
			throw new DuplicateVarException(input, var);
	}
	
	/**
	 * Sets a var as a global in the current context.
	 * @param global
	 * @throws GlobalException 
	 */
	public void addGlobal(Token var) throws GlobalException {
		addGlobal(var.getText());
	}
	
	/**
	 * Sets a var as a global in the current context.
	 * @param global
	 * @throws GlobalException 
	 */
	public void addGlobal(String global) throws GlobalException {
		String cc = getCurrentContext();
		if(!globals.containsKey(cc)) 
			globals.put(cc, new ArrayList<Global>());
		ArrayList<Global> arr = globals.get(cc);
		Global gl = new Global(global, input.mark());
		arr.add(gl);
	}
	
	/**
	 * Does this var exists in current context?
	 * @param var
	 * @return
	 */
	public boolean existsInCC(Token var) {
		return existsInCC(var.getText());
	}
	
	/**
	 * Does this var exists in current context?
	 * @param var
	 * @return
	 */
	public boolean existsInCC(String var) {
		return existsInContext(getCurrentContext(), var);
	}
	
	/**
	 * Does this var exists in the specified context?
	 * @param var
	 * @return
	 */
	private boolean existsInContext(String ctx, String var) {
		String tempCtx = ctx;
		while(true) {
			ArrayList<String> list = context.get(tempCtx);
			if(list != null && list.contains(var))
				return true;
			int i = tempCtx.lastIndexOf(SEP);
			if(i > 0)
				tempCtx = tempCtx.substring(0, i);
			else
				return false;
		}
	}
	
	private String goUp(String context) {
		int i = context.lastIndexOf(SEP);
		if(i < 0)
			return context;
		return context.substring(0, i);
	}
	
	private String innerContext(String context) {
		int i = context.lastIndexOf(SEP);
		if(i < 0)
			return context;
		return context.substring(i + 1, context.length());
	}
	
	@SuppressWarnings("unchecked")
	public void processGlobals() throws GlobalException {
		for (String con : context.keySet()) {
			ArrayList<String> list = context.get(con);
			ArrayList<Global> glos = globals.get(con);
			ArrayList<String> tempList = (ArrayList<String>) list.clone();
			if(innerContext(con).startsWith("$")) {
				for(String var : tempList) {
					if(existsInContext(goUp(con), var)) {
						list.remove(var);
						if(glos != null)
							glos.remove(var);
					}
				}
			}
			if(glos != null && glos.size() > 0) {
				for (Global glo : glos) {
					if(!existsInContext(con, glo.var)) {
						input.rewind(glo.mark);
						throw new GlobalException(input, "assignment to undefined " +
								"variable: " + glo);
					}
					list.remove(glo);
				}
			}
		}
	}
	
	@Override
	public String toString() {
		StringBuilder b = new StringBuilder("- ProgmaSymbolTable\n");
		for (String key : context.keySet()) {
			ArrayList<String> val = context.get(key);
			b.append("--- " + key + "\n");
			for (String s : val) {
				b.append("----- " + s + "\n");
			}
		}
		return b.toString();
	}
	
	private class Global {
		public String var;
		public int mark;
		
		public Global(String var, int mark) {
			this.var = var;
			this.mark = mark;
		}
		
		@Override
		public String toString() {
			return var;
		}
	}
}
